package main

import (
	"context"
	"encoding/json"
	"funding-rate-arbitrage/persistence"
	"strconv"
	"sync"
	"time"

	"gitee.com/hacker61/go-binance/v2"
	"gitee.com/hacker61/go-binance/v2/futures"
)

var (
	binanceClient *binance.Client
	futuresClient *futures.Client
	fundingRate   *FundingRate
	ptrAccount    *Account
	ppersistence  persistence.Persistence
)

var (
	mpArbitrageObj map[string]*SymbolArbitrage
)

func main_init() {
	binanceClient = binance.NewClient(conf.ApiKey, conf.SecretKey)
	futuresClient = futures.NewClient(conf.ApiKey, conf.SecretKey)
	fundingRate = NewFundingRate(futuresClient, &conf.FundingRateOpt)

	mpArbitrageObj = make(map[string]*SymbolArbitrage)

	if ppersistence == nil {
		ppersistence = persistence.PersistenceMysql{}.Init(conf.MysqlOpt.Host, conf.MysqlOpt.Port, conf.MysqlOpt.User, conf.MysqlOpt.Passwd, conf.MysqlOpt.DBName)
	}
	positions, err := ppersistence.QueryAllArbitragePosition()
	if err != nil {
		Error.Println(err)
	}
	for _, position := range positions {
		arbitrageObj := NewSymbolArbitrage(position.Symbol, binanceClient, futuresClient)
		err = arbitrageObj.ChangeState2Position(position)
		if err != nil {
			Error.Println(err)
			continue
		}
		go arbitrageObj.StartRuntine()
		mpArbitrageObj[position.Symbol] = arbitrageObj
	}

	ptrAccount = NewAccount()
	err = ptrAccount.GetAccountSpotBalances()
	if err != nil {
		Error.Println(err)
	}
}

func init() {
	conf_init()
	order_init()
	logger_init()
	main_init()
}

func main() {
	wg := sync.WaitGroup{}

	pFundingRate := NewFundingRate(futuresClient, &(conf.FundingRateOpt))
	for symbol, arbitrageObj := range mpArbitrageObj {
		if fundingRate, bOk := pFundingRate.SymbolFundingRates[symbol]; bOk {
			arbitrageObj.FundingRate = fundingRate
		}
	}

	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			<-pFundingRate.getTicker.C
			pFundingRate.getSymbolsFundingRate()
			for symbol, arbitrageObj := range mpArbitrageObj {
				if fundingRate, bOk := pFundingRate.SymbolFundingRates[symbol]; bOk {
					arbitrageObj.FundingRate = fundingRate
				}
			}
		}
	}()
	// 将从数据库读取的持仓数据加入到

	binance.WebsocketKeepalive = true
	var doneC, fdoneC, doneC2, fdoneC2 chan struct{}
	var err, ferr error
	var binanceWsAllBookTickerServe, futuresWsAllBookTickerServe, binanceWsUserDataServ, futuresWsUserDataServ func()
	binanceWsAllBookTickerServe = func() {
		doneC, _, err = binance.WsAllBookTickerServe(func(event *binance.WsBookTickerEvent) {
			_, bOk := pFundingRate.CreateSymbols[event.Symbol]
			arbitrageObj, bOk2 := mpArbitrageObj[event.Symbol]
			if bOk || (bOk2 && arbitrageObj.stateMachine.PositionState == PositionState_Position) {
				if !bOk2 {
					arbitrageObj = NewSymbolArbitrage(event.Symbol, binanceClient, futuresClient)
					go arbitrageObj.StartRuntine()
					mpArbitrageObj[event.Symbol] = arbitrageObj
				}
				arbitrageObj.binanceBookTickerC <- event
			}
		}, func(e error) {
			Error.Println(e)
			if doneC != nil {
				doneC <- struct{}{}
			}
			if binanceWsAllBookTickerServe != nil {
				binanceWsAllBookTickerServe()
			}
		})
		if err != nil {
			Error.Printf("binance WsAllBookTickerServe error: %v\n", err)
			panic(err)
		}
	}
	futuresWsAllBookTickerServe = func() {
		fdoneC, _, ferr = futures.WsAllBookTickerServe(func(event *futures.WsBookTickerEvent) {
			_, bOk := pFundingRate.CreateSymbols[event.Symbol]
			arbitrageObj, bOk2 := mpArbitrageObj[event.Symbol]
			if bOk || (bOk2 && arbitrageObj.stateMachine.PositionState == PositionState_Position) {
				if !bOk2 {
					arbitrageObj = NewSymbolArbitrage(event.Symbol, binanceClient, futuresClient)
					go arbitrageObj.StartRuntine()
					mpArbitrageObj[event.Symbol] = arbitrageObj
				}
				arbitrageObj.futuresBookTickerC <- event
			}
		}, func(e error) {
			Error.Println(e)
			if fdoneC != nil {
				fdoneC <- struct{}{}
			}
			if futuresWsAllBookTickerServe != nil {
				futuresWsAllBookTickerServe()
			}
		})
		if ferr != nil {
			Error.Printf("futures WsAllBookTickerServe error: %v\n", ferr)
			panic(ferr)
		}
	}
	binanceWsAllBookTickerServe()
	futuresWsAllBookTickerServe()

	listenerKey, err := binanceClient.NewStartUserStreamService().Do(context.Background())
	if err != nil {
		Error.Println(err)
		panic(err)
	}

	flistenerKey, ferr := futuresClient.NewStartUserStreamService().Do(context.Background())
	if ferr != nil {
		Error.Println(ferr)
		panic(ferr)
	}

	defer func() {
		err = binanceClient.NewCloseUserStreamService().ListenKey(listenerKey).Do(context.Background())
		ferr = futuresClient.NewCloseUserStreamService().ListenKey(flistenerKey).Do(context.Background())
		if err != nil {
			Error.Println(err)
		}
		if ferr != nil {
			Error.Println(ferr)
		}
	}()

	ticker := time.NewTicker(30 * time.Minute)
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			<-ticker.C
			err = binanceClient.NewKeepaliveUserStreamService().ListenKey(listenerKey).Do(context.Background())
			if err != nil {
				Error.Println(err)
				panic(err)
			}
			ferr = futuresClient.NewKeepaliveUserStreamService().ListenKey(flistenerKey).Do(context.Background())
			if ferr != nil {
				Error.Println(ferr)
				panic(ferr)
			}
		}
	}()

	/*
		binanceWsUserDataServ = func() {
			doneC2, _, err = binance.WsExecutionReportServe(listenerKey, func(event *binance.WsExecutionReportEvent) {
				if event.OrderStatus == binance.OrderStatusTypeFilled {
					if arbitrageObj, bOk := mpArbitrageObj[event.Symbol]; bOk {
						Info.Println(*event)
						select {
						case <-arbitrageObj.chanBinanceFilledOrder:
							arbitrageObj.chanBinanceFilledOrder <- *event
						default:
							arbitrageObj.chanBinanceFilledOrder <- *event
						}
					}
				}
			}, func(e error) {
				Error.Println(e)
				if doneC2 != nil {
					doneC2 <- struct{}{}
				}
				if binanceWsUserDataServ != nil {
					binanceWsUserDataServ()
				}
			})
			if err != nil {
				Error.Printf("binance WsExecutionReportServe error: %v\n", err)
				panic(err)
			}
		}
	*/

	//Todo
	binanceWsUserDataServ = func() {
		doneC2, _, err = binance.WsUserDataServe(listenerKey, func(message []byte) {
			dataMap := make(map[string]interface{})
			err := json.Unmarshal(message, &dataMap)
			if err != nil {
				Error.Println(err)
				return
			}
			if eType, bOk := dataMap["e"]; bOk {
				evtType := eType.(string)
				if evtType == "executionReport" {
					event := new(binance.WsExecutionReportEvent)
					err := json.Unmarshal(message, &event)
					if err != nil {
						Error.Println(err)
						return
					}
					Info.Println(*event)
					if event.OrderStatus == binance.OrderStatusTypeFilled {
						if arbitrageObj, bOk := mpArbitrageObj[event.Symbol]; bOk {
							select {
							case <-arbitrageObj.chanBinanceFilledOrder:
								arbitrageObj.chanBinanceFilledOrder <- *event
							default:
								arbitrageObj.chanBinanceFilledOrder <- *event
							}
						}
					}
				} else if evtType == "outboundAccountPosition" {
					event := new(WsOutboundAccountPositionEvent)
					err := json.Unmarshal(message, &event)
					if err != nil {
						Error.Println(err)
						return
					}
					Info.Println(*event)
					for _, balance := range event.Balances {
						free, err := strconv.ParseFloat(balance.Free, 64)
						if err != nil {
							Error.Println(err)
							continue
						}
						locked, err := strconv.ParseFloat(balance.Locked, 64)
						if err != nil {
							Error.Println(err)
							continue
						}
						err = ptrAccount.UpdateSpotAccount(balance.Asset, free, locked)
						if err != nil {
							Error.Println(err)
						}
					}
				}
			}
		}, func(e error) {
			Error.Println(e)
			if doneC2 != nil {
				doneC2 <- struct{}{}
			}
			if binanceWsUserDataServ != nil {
				binanceWsUserDataServ()
			}
		})
	}

	futuresWsUserDataServ = func() {
		fdoneC2, _, ferr = futures.WsUserDataServe(flistenerKey, func(event *futures.WsUserDataEvent) {
			if event.Event != futures.UserDataEventTypeOrderTradeUpdate {
				return
			}
			orderTradeUpdate := event.OrderTradeUpdate
			Info.Println(orderTradeUpdate)
			if orderTradeUpdate.Status == futures.OrderStatusTypeFilled {
				if arbitrageObj, bOk := mpArbitrageObj[orderTradeUpdate.Symbol]; bOk {
					select {
					case <-arbitrageObj.chanFuturesOrderTradeUpdate:
						arbitrageObj.chanFuturesOrderTradeUpdate <- orderTradeUpdate
					default:
						arbitrageObj.chanFuturesOrderTradeUpdate <- orderTradeUpdate
					}
				}
			}
		}, func(e error) {
			Error.Println(e)
			if fdoneC2 != nil {
				fdoneC2 <- struct{}{}
			}
			if futuresWsUserDataServ != nil {
				futuresWsUserDataServ()
			}
		})
		if ferr != nil {
			Error.Printf("futures WsUserDataServe error: %v\n", ferr)
			panic(ferr)
		}
	}

	binanceWsUserDataServ()
	futuresWsUserDataServ()

	wg.Wait()
	doneC <- struct{}{}
	fdoneC <- struct{}{}
	doneC2 <- struct{}{}
	fdoneC2 <- struct{}{}
}
